package com.esdk.sql.orm;

import com.esdk.esdk;
import com.esdk.interfaces.Callback;
import com.esdk.sql.*;
import com.esdk.utils.EasyReflect;
import com.esdk.utils.EasyStr;
import com.esdk.utils.RedisClient;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.util.*;
import java.util.Map.Entry;
/***
 * @author 范飞宇
 * @since 2006.?.?
 */
@SuppressWarnings("ALL")
abstract public class AbstractSelect<S extends AbstractSelect,R extends ParentRow> extends SelectWrapper<S,R> implements ISelect,IPageSelect{
  private boolean isOutputAllFields=true;
  private boolean isNullable=true; //high priority, notice that null is different with blank for db sql.
  private boolean isAllowBlank=true; //middle priority
	private boolean isCachable=esdk.prop.getBoolean("db_cachable",true);
  abstract public Object getMetaData();
  private String[] fieldNames;
  private Class[] fieldTypes;
  //private Columns columns=new Columns();
  protected HashMap<String,Object> fieldMap=new LinkedHashMap();

  protected AbstractSelect(Table table) {
    super(table);
  }

  protected AbstractSelect(String tablename,boolean isTop,Connection conn) {
    super(tablename,conn);
    setTop(isTop?DefaultLimit:0);
  }

  protected AbstractSelect(String tablename,ORMSession ormsession) {
    super(tablename,ormsession.getConnection());
    useSessionCache(true);
  }

  protected AbstractSelect(String tablename,boolean isTop,ORMSession ormsession) {
    super(tablename,ormsession.getConnection());
    setTop(isTop?DefaultLimit:0);
		useSessionCache(true);
  }

  protected AbstractSelect(Table table,Connection conn) {
    super(table,conn);
  }

  protected AbstractSelect(String tablename,String alias,String joinType) {
    super(new Table(tablename,alias,joinType),null);
  }

  protected AbstractSelect(String tablename,String alias,boolean isJoin) {
    super(new Table(tablename,alias,isJoin),null);
  }

  protected AbstractSelect(String tablename,String joinType) {
    super(new Table(tablename,null,joinType),null);
  }

  protected AbstractSelect(String tablename,boolean isJoin) {
    super(new Table(tablename,isJoin),null);
  }

  protected AbstractSelect(String tablename,Connection conn) {
    super(tablename,conn);
  }

  public S setPrimaryKey(Long pkid) {
 	 fieldMap.put(getPrimaryKeyFieldName(),pkid);
 	 return (S)this;
 }

  public S setPrimaryKey(Long... values) {
		if(esdk.obj.isEmpty(values)) return (S)this;
  	super.addIn(getPrimaryKeyFieldName(),values);
  	return (S)this;
 }

  public S in(String fieldName,List<?> values){
		return in(fieldName,esdk.array.toArray(values));
	}

  public S in(String fieldName,Number...values){
		fieldMap.remove(fieldName); // 清除field=value的条件
		super.removeCondition(fieldName);// 清除所有该字段的查询条件，以解决parse()后无法清除select查询条件的问题
		return super.addIn(fieldName,values);
  }

 public S in(String fieldName,Date...values){
	 fieldMap.remove(fieldName); // 清除field=value的条件
	 super.removeCondition(fieldName);// 清除所有该字段的查询条件，以解决parse()后无法清除select查询条件的问题
	 return super.addIn(fieldName,values);
 }

 public S in(String fieldName,Time...values){
	 fieldMap.remove(fieldName); // 清除field=value的条件
	 super.removeCondition(fieldName);// 清除所有该字段的查询条件，以解决parse()后无法清除select查询条件的问题
		return super.addIn(fieldName,values);
 }

  public S in(String fieldName,String...values){
		fieldMap.remove(fieldName); // 清除field=value的条件
		super.removeCondition(fieldName);// 清除所有该字段的查询条件，以解决parse()后无法清除select查询条件的问题
  	return super.addIn(fieldName,values);
  }

	public S in(String fieldName,Object ...values){
		fieldMap.remove(fieldName); // 清除field=value的条件
		super.removeCondition(fieldName);// 清除所有该字段的查询条件，以解决parse()后无法清除select查询条件的问题
		return super.addIn(fieldName,values);
	}

  public S in(String fieldName,ISelect select){
		fieldMap.remove(fieldName); // 清除field=value的条件
		super.removeCondition(fieldName);// 清除所有该字段的查询条件，以解决parse()后无法清除select查询条件的问题
  	return super.addIn(fieldName,select);
  }

	public S exists(ISelect select) {
		return exists(this.getPrimaryKeyFieldName(),select);
	}

	public S exists(String fieldName,ISelect select) {
		fieldMap.remove(fieldName);
		super.removeCondition(fieldName);
		return super.exists(fieldName,select);
	}

	public S notExists(ISelect select) {
		return notExists(this.getPrimaryKeyFieldName(),select);
	}

	public S notExists(String fieldName,ISelect select) {
		fieldMap.remove(fieldName);
		super.removeCondition(fieldName);
		return super.notExists(fieldName,select);
	}

	@Override
	public S addIn(String fieldName,Number...values){
		return super.addIn(fieldName,values);
	}

	@Override
	public S addIn(String fieldName,Date...values){
		return super.addIn(fieldName,values);
	}

	@Override
	public S addIn(String fieldName,Time...values){
		return super.addIn(fieldName,values);
	}

	@Override
	public S addIn(String fieldName,String...values){
		return super.addIn(fieldName,values);
	}

	@Override
	public S addIn(String fieldName,ISelect select){
		return super.addIn(fieldName,select);
	}

	@Override
	public ABResultSet<IRow> toABResultSet() throws SQLException{
		ABResultSet<IRow> result=super.toABResultSet();
		result.setSelect(this);
		return result;
	}

  public ParentResultSet<R> toParentResultSet() throws SQLException {
  	ParentResultSet parentResultSet=null;
		if(useSessionCache){
			ORMSession ormSession=ORMSessionFactory.getORMSession(this.getConnection());
			parentResultSet=esdk.obj.or(ormSession.getParentResultSet(this),()->ormSession.putParentResultSet(this,createParentResultSet(toResultSet())));
		}
  	else if(isCachable&&useCacheSec>0){
  		RedisClient redis=new RedisClient();
			String cacheKey=SQLAssistant.getCacheKey(this);
  		if(redis.exists(cacheKey)) {
  			parentResultSet=(ParentResultSet)redis.getObj(cacheKey);
  		}
  		if(parentResultSet==null) {
  			redis.del(redis.keys(cacheKey+"*"));//注意一定要有*号结尾
				parentResultSet=createParentResultSet(toResultSet());
				parentResultSet.last();
				redis.setObj(cacheKey,parentResultSet,useCacheSec);
			}
  		redis.close();
  	}else{
			parentResultSet=createParentResultSet(toResultSet());
		}
  	parentResultSet.setSelect(this);
  	return parentResultSet;
  }

	@Override public ABRowSet<R> toRowSet() throws SQLException{
		if(!useSessionCache){
			this.select.showFullSql(true);
			return new ABRowSet(toParentResultSet());
		}
		else{
			ORMSession ormSession=ORMSessionFactory.getORMSession(this.getConnection());
			return esdk.obj.or(ormSession.getABRowSet(this),()->ormSession.putABRowSet(this,new ABRowSet(toParentResultSet())));
		}
	}

	/**解决非索引原因导致的获取数据量过大导致查超时异常(即返回错误：Query execution was interrupted, maximum statement execution time exceeded)的问题*/
	public ABRowSet<R> toRowSet(int limit) throws SQLException{
		if(!useSessionCache){
			ABRowSet<R> totalRs=null;
			if(select.getRowsLimit()==-1 && select.getRowsLimit()!=limit)
				setRowsLimit(limit);
			boolean hasMoreQuery=true;
			do{
				if(totalRs==null){
					totalRs=new ABRowSet(toParentResultSet());
					hasMoreQuery=totalRs.size()>=limit; //正常应该只会是=limit，否则就是有bug
				}
				else{
					select.setRowsOffset(totalRs.size());
					totalRs.addAll(toParentResultSet());
					if(totalRs.size()%limit>0)
						hasMoreQuery=false;
				}
			}while(hasMoreQuery);
			return totalRs;
		}
		else{
			ORMSession ormSession=ORMSessionFactory.getORMSession(this.getConnection());
			return esdk.obj.or(ormSession.getABRowSet(this),()->ormSession.putABRowSet(this,new ABRowSet(toParentResultSet())));
		}
	}

  public List<R> toList(int start,int limit) throws Exception {
    return toParentResultSet().getSubList(start,limit);
  }

  public List<R> toList() throws Exception {
    return list();
  }

  public List<R> list() throws Exception {
    return toParentResultSet().gainAllRow();
  }

	public R getFirstRow() {
		return getFirstRow(false);
	}

  public R getFirstRow(boolean isCreateInstance) {
		R result;
		int oriRowLimit=getSelect().getTop();
		setRowsLimit(1);
		try{
			result=toParentResultSet().getFirstRow(isCreateInstance);
			if(result!=null&&!result.isExistRecord()){
				result.setConnection(getConnection());
				result.load(this);
			}
		}catch(Exception e){
			throw new SQLRuntimeException(e.toString()+"，SQL语句："+select.getSQL(),e);
		}
		select.setTop(oriRowLimit);
  	return result;
  }

  /**获取第一条记录的第一个字段值*/
  public Integer getFirstInteger() throws Exception {
  	return getFirstValue(Integer.class);
  }

  /**获取第一条记录的第一个字段值，并指定默认值*/
  public Integer getFirstInteger(Integer defVal) throws Exception {
  	return getFirstValue(Integer.class,defVal);
  }

  /**获取第一条记录的第一个字段值*/
  public String getFirstString() throws Exception {
  	return getFirstValue(String.class);
  }

  /**获取第一条记录的第一个字段值，并指定默认值*/
  public Double getFirstDouble(Double defVal) throws Exception {
  	return getFirstValue(Double.class,defVal);
  }

  /**获取第一条记录的第一个字段值*/
  public Double getFirstDouble() throws Exception {
  	return getFirstValue(Double.class);
  }

  /**获取第一条记录的第一个字段值，并指定默认值*/
  public String getFirstLong(String defVal) throws Exception {
  	return getFirstValue(String.class,defVal);
  }

  /**获取第一条记录的第一个字段值*/
  public Long getFirstLong() throws Exception{
  	return getFirstValue(Long.class);
  }

  /**获取第一条记录的第一个字段值，并指定默认值*/
  public Long getFirstLong(Long defVal) throws Exception {
  	return getFirstValue(Long.class,defVal);
  }

  /**获取第一条记录的第一个字段值，并指定默认值*/
  public <V> V getFirstValue(Class<V> cls) throws Exception{
  	R row=getFirstRow();
  	if(row==null)
  		return null;
  	else {
  		String field=(String)row.record.keySet().iterator().next();
  		return esdk.obj.valueOf(cls,row.get(field));
  	}
  }

  /**获取第一条记录的第一个字段值*/
  public <V> V getFirstValue(Class<V> cls,V defVal) throws Exception {
  	return esdk.obj.or(getFirstValue(cls),defVal);
  }

  protected ParentRow getFirstRowNoSession(){
  	return (ParentRow)getFirstRowNoSession(false);
  }

  protected ParentRow getFirstRowNoSession(boolean isCreateInstance) {
  	ParentRow result=null;
  	int oldTop=getTop();
  	try{
  		setTop(1);
			ParentResultSet prs=this.toParentResultSet();
			setTop(oldTop);
			if(prs.first())
				result=(ParentRow)prs.getCurrent();
			else if(isCreateInstance) {
				result=prs.createRowInstance();
				result.setConnection(getConnection());
				result.load(this);
			}
			prs.close();
			return (ParentRow)result;
		}
		catch(Exception e){
			throw new SQLRuntimeException(e);
		}
  }

  public boolean isExistRecord()throws SQLException{
    return count()>0;
  }

	protected ParentResultSet<R> createParentResultSet(ResultSet rs){
		Class resultsetClass=esdk.reflect.loadClass(this.getClass().getPackage().getName().concat(".").concat(getTableBeanName().concat("ResultSet")));
		ParentResultSet<R> result=(ParentResultSet<R>)esdk.reflect.safeNewInstance(resultsetClass,rs);
		return result;
	}

  private String getTableBeanName() {
    String clsname=this.getClass().getSimpleName();
    return clsname.substring(0,clsname.indexOf("Select"));
    /*return getTable().getTableName();*/
  }


  public boolean isOutputAllFields() {
    return isOutputAllFields||select.isOutputAllColumns();
  }

  public S setOutputAllFields(boolean value) {
    isOutputAllFields=value;
    return (S)this;
  }

	public S clearColumns() {
		setOutputAllFields(false);
		select.clearColumns();
		return (S)this;
	}


	public S setCamelCaseColumns(String...columnNames){
		setOutputAllFields(false);
		Column[] columns=new Column[columnNames.length];
		for(int i=0;i<columnNames.length;i++){
			columns[i]=new Column(this.getTableAliasName(),columnNames[i],null,EasyStr.toCamelCase(columnNames[i]));
		}
		select.setColumns(columns);
		return (S)this;
	}

  public S setColumns(String columnnames) {
		setColumns(EasyStr.split(columnnames));
    return (S)this;
  }

  @Override public S setColumns(Column... cols){
		setOutputAllFields(false);
  	super.setColumns(cols);
  	return (S)this;
  }

	@Override public S setColumns(String... columnNames){
		setOutputAllFields(false);
		super.setColumns(columnNames);
		return (S)this;
	}

	public S setColumns(){
		setOutputAllFields(false);
		return (S)this;
	}

	/**
	 * 实现多个别名字段的输出：select name as c1,code as c2 ...
	 * @param columnsMap:可输出多个字段的别名
	 * */
	public S setColumns(Map<String,String> columnsMap){
		setOutputAllFields(false);
		for(String columnName:columnsMap.keySet()) {
			String aliasName=esdk.obj.or(columnsMap.get(columnName),columnName);
			super.addColumnWithAlias(columnName,aliasName);
		}
		return (S)this;
	}

	@Override public S addColumns(String... columnnames){
		super.addColumns(columnnames);
		return (S)this;
	}

  @Override public S addColumnWithAlias(String columnname,String alias){
    super.addColumnWithAlias(columnname,alias);
    return (S)this;
  }

  @Override public S addColumn(Column column){
    super.addColumn(column);
    return (S)this;
  }

  @Override public S addColumn(ISQL select,String aliasName){
		this.select.getTable().createAliasName();
  	super.addColumn(select,aliasName);
    return (S)this;
  }

  @Override public void clear() {
  	super.clear();
  	this.fieldMap.clear();
  }

  /**清除session、redis缓存*/
	public S clearCache(){
		if(useSessionCache)
    	ORMSessionFactory.removeORMSession(getConnection());
    if(useCacheSec>0||isCachable) {
    	RedisClient redis=new RedisClient();
			String cacheKey=SQLAssistant.getCacheKey(this);
  		if(redis.exists(cacheKey)) {
  			redis.del(redis.keys(cacheKey+"*"));//注意一定要有*号结尾
  		}
  		redis.close();
    }
		return (S)this;
  }

  /**清除order by语句*/
	public S clearOrderBy() {
		this.select.clearOrderBy();
		return (S)this;
	}

	@Override
	public S clearCondition(){
		this.fieldMap.clear();
		this.select.clearCondition();
		return (S)this;
	}

  public void parse(){
		if(isOutputAllFields) {
			Column[] cols=this.select.getColumns().toArray();
			this.select.getColumns().clear();
			addColumns("*");
			addColumns(cols);
		}
		for(Iterator iter=fieldMap.keySet().iterator();iter.hasNext();){
			String fieldname=(String)iter.next();
			Object obj=fieldMap.get(fieldname);
			if(obj==null&&isNullable())
				this.addCondition(new NullCondition(this.createField(fieldname)));
			else if(obj instanceof Field field){
				super.addEq(fieldname,field);
			}
			else if(obj instanceof SmartBetween smartBetween) {
				if(!smartBetween.getStmtSql().equals("1=1"))
					this.addCondition(smartBetween);
			}
			else if((obj!=null && !"".equals(obj.toString())) || isAllowBlank()){
				if(obj instanceof ILogic)
					this.addCondition((ILogic)obj);
				else
					super.addEq(fieldname,obj);
			}
			else if(isAllowBlank()&&"".equals(obj))
				super.addEq(fieldname,obj);
		}
	}

	@Override
	public S eq(String fieldName,Number value){
		fieldMap.put(fieldName,value);
		return (S)this;
	}

	@Override
	public S eq(String fieldName,String value){
		fieldMap.put(fieldName,value);
		return (S)this;
	}
	@Override
	public S eq(String fieldName,Boolean value){
		fieldMap.put(fieldName,value);
		return (S)this;
	}
	@Override
	public S eq(String fieldName,Object value){
		fieldMap.put(fieldName,value);
		return (S)this;
	}

	public S eq(String fieldName,Field field){
		fieldMap.put(fieldName,field);
		return (S)this;
	}

	/**多个字段、相同关键字的or条件的模糊查询，例如:where name like abc% or code like abc% */
	@Override
	public S likeOr(String[] fieldNames,String keyword){
		for(String fieldName: fieldNames){
			fieldMap.remove(fieldName); // 清除field=value的条件
		}
		return super.likeOr(fieldNames, keyword);
	}

	/**一个字段、多个关键字的or条件的模糊查询，例如:where name like abc% or name like god% */
	@Override
  public S likeOr(String fieldName,String...keywords){
  	fieldMap.remove(fieldName); // 清除field=value的条件
		super.removeCondition(fieldName);// 清除所有该字段的查询条件，以解决parse()后无法清除select查询条件的问题
  	return super.likeOr(fieldName,keywords);
  }

  @Override
  public S likeAnd(String columnName,String...keywords){
  	return super.likeAnd(columnName,keywords);
  }

  @Override
  public S addLike(String fieldName,String value){
  	return super.addLike(fieldName,value);
  }

  public String getPrimaryKeyFieldName() {
  	return (String)EasyReflect.getFieldValue(getMetaData(),"PrimaryKey");
  }

	@Override public S orLike(Object...args){
		return super.orLike(args);
	}

	@Override public S orEq(Object...args){
		return super.orEq(args);
	}

	@Override public S or(Object...args){
		return super.or(args);
	}

	public S setNullable(boolean isNullable){
		this.isNullable = isNullable;
		return (S)this;
	}

	public boolean isNullable(){
		return isNullable;
	}

	public S setAllowBlank(boolean isAllowBlank){
		this.isAllowBlank = isAllowBlank;
		return (S)this;
	}

	public boolean isAllowBlank(){
		return isAllowBlank;
	}

	/**
	 * 只匹配已存在的字段，忽略null字段
	 * 支持between条件，但必须是Begin|Min|End|Max结尾的Key名称
	 * 日期范围会自动把日期最大值的时分秒改为当天最后一秒，注意前提是时间值为00:00:00才会自动修改。
	 * 如果值为数组会使用in或or查询
	 * */
  public S load(Map map) {
		if(map==null)
			return (S)this;
  	HashSet<String> skipFieldSet=new HashSet();
  	for(Iterator<Entry> iter=map.entrySet().iterator();iter.hasNext();) {
  		Entry<String,Object> item=iter.next();
  		String field=esdk.str.toUnderlineCase(item.getKey());
  		int index=esdk.array.indexOf(getNames(),field);
  		if(index>=0) {
  			if(item.getValue()==null && !isNullable)
  				continue;
  			else if(item.getValue().toString().length()==0 && !this.isAllowBlank)
  				continue;
				else if(item.getValue() instanceof String && ((String)item.getValue()).contains("*")) { //把*号变成%号，改为like方式模糊查询。
					addLikeCondition(field,((String)item.getValue()).replaceAll("\\*","%"));
				}else if(item.getValue() instanceof Object[] || item.getValue() instanceof String[]){
					if(field.matches(".*?_(names|ids)")){ //如果值为数组会使用in或or查询
						likeOr(field,esdk.sql.wildcard((String[])item.getValue()));
					}
					else
						addIn(field,(Object[])item.getValue());
				}else {
					eq(field,esdk.obj.valueOf(getFieldTypes()[index],item.getValue()));
  			}
  		}
			else if(item.getKey().matches(".*?\\[\\]$")) {
				field=field.replace("[]","");
				String[] valRange=((String)item.getValue()).split(",");
				if(esdk.math.isNumeric(valRange[0]))
					eq(field,new SmartBetween(this.createField(field),esdk.math.toNumber(valRange[0]),esdk.math.toNumber(valRange[1])));
				else{
					Date minDateTime=esdk.time.toDate(valRange[0]);
					Date maxDateTime=esdk.time.getEndDate(esdk.time.toDate(valRange[1]));
					eq(field,new SmartBetween(this.createField(field),minDateTime,maxDateTime));
				}
			}
  		else if(item.getKey().matches(".*?(Begin|Min|End|Max)$")) {
  			field=esdk.str.toUnderlineCase(item.getKey().replaceAll("(Begin|Min|End|Max)$",""));
  			if(skipFieldSet.contains(field))
  				continue;
  			index=esdk.array.indexOf(getNames(),field);
  			String minFieldName=item.getKey().endsWith("End")?item.getKey().replaceFirst("End$","Begin"):item.getKey().replaceFirst("Max$","Min");
  			String maxFieldName=item.getKey().endsWith("Begin")?item.getKey().replaceFirst("Begin$","End"):item.getKey().replaceFirst("Min$","Max");
  			Object minValue=esdk.obj.valueOf(getFieldTypes()[index],map.get(minFieldName));
  			Object maxValue=esdk.obj.valueOf(getFieldTypes()[index],map.get(maxFieldName));
  			if((minValue!=null && minValue instanceof Number)||(maxValue !=null && maxValue instanceof Number)) {
  				eq(field,new SmartBetween(this.createField(field),(Number)minValue,(Number)maxValue));
  			}
  			else {
  				Date maxDateTime=(Date)maxValue;
  				if(maxValue!=null && esdk.time.format(maxDateTime,"HH:mm:ss").equals("00:00:00"))
  					maxDateTime=esdk.time.getEndDate(maxDateTime);
					eq(field,new SmartBetween(this.createField(field),(Date)minValue,maxDateTime));
  			}
  			skipFieldSet.add(field);
  		}
  	}
  	return (S)this;
  }

  public S load(IRow row) {
		if(row==null)
			return (S)this;
  	this.load(row.toMap(false));
//  	EasyReflect.copyBeanProperties(obj,this,false,true,esdk.str.distinct(RowFacility.Keywords,"TableAliasName","Session","OutputAllFields","ColumnsAsJavaBean"));
  	return (S)this;
  }

  public S load(Object obj) {
		if(obj==null)
			return (S)this;
  	this.load(esdk.bean.toMap((Serializable)obj));
//  	EasyReflect.copyBeanProperties(obj,this,false,true,esdk.str.distinct(RowFacility.Keywords,"TableAliasName","Session","OutputAllFields","ColumnsAsJavaBean"));
  	return (S)this;
  }

  public S load(AbstractSelect abstractSelect) {
		if(abstractSelect==null)
			return (S)this;
  	this.fieldMap=(HashMap)abstractSelect.fieldMap.clone();
  	this.isOutputAllFields=abstractSelect.isOutputAllFields;
  	this.isNullable=abstractSelect.isNullable;
  	this.isAllowBlank=abstractSelect.isAllowBlank;
  	this.select=abstractSelect.select.clone();
  	return (S)this;
  }

	@Override
	/** 会自动把字段等于某个值的条件清空，因为不可能出现又等于又like的情况，这样是毫无意义的 */
	public S addLikeCondition(String fieldName,String value){
		fieldMap.remove(fieldName); // 清除field=value的条件
		super.removeCondition(fieldName);// 清除所有该字段的查询条件，以解决parse()后无法清除select查询条件的问题
		return super.addLikeCondition(fieldName,value);
	}

	@Override
	/** 会自动把字段等于某个值的条件清空 */
	public S notLike(String fieldName,String value){
		fieldMap.remove(fieldName); // 清除field=value的条件
		super.removeCondition(fieldName);// 清除所有该字段的查询条件，以解决parse()后无法清除select查询条件的问题
		return super.notLike(fieldName,value);
	}

	@Override
	public S removeCondition(String fieldName){
		fieldMap.remove(fieldName);
		return super.removeCondition(fieldName);
	}

	public S useCache(){
		return useCache(true);
	}
	
	@Override
	public S useCache(int sec){
		return super.useCache(sec);
	}

	public S useSessionCache(boolean useSessionCache){
		return super.useSessionCache(useSessionCache);
	}

	@Override
	public S useCache(boolean ifUseCache){
		return super.useCache(ifUseCache);
	}

	public boolean hasColumn(String column){
		return EasyReflect.hasPropertyName(this.getMetaData().getClass(),esdk.str.toCamelCase(column,true));
	}

	public String[] getNames() {
		if(fieldNames==null)
			fieldNames=(String[])EasyReflect.getDeclaredFieldValue(getMetaData(),"FieldNames");
		return fieldNames;
	}

	public Class[] getFieldTypes() {
		if(fieldTypes==null)
			fieldTypes=(Class[])EasyReflect.getDeclaredFieldValue(getMetaData(),"FieldTypes");
		return fieldTypes;
	}

	/**
	 * 使用窗口函数rownumber() over()，排序后对指定多个字段分组
	 */
	public S useRowNumberOver(String partitionFields, String orderByField, SortDirEnum sortDir, String alias){
		this.select.getTable().createAliasName();
		setTableName(esdk.str.format("(SELECT *,{} FROM {})",
				new RowNumber(partitionFields, orderByField, sortDir.name(), alias).toString(),getTable().getTableName()
		));
		return (S) this;
	}

	/**
	 * 使用窗口函数rownumber() over()，排序后对指定多个字段分组，并只获取分组后的第一条记录
	 */
	public S rowNumberOverFirst(String partition, String orderByField, SortDirEnum sortDir){
		this.select.getTable().createAliasName();
		setTableName(esdk.str.format("(SELECT * FROM (SELECT *,{} FROM {}) {} WHERE {} = 1)"
			,new RowNumber(partition, orderByField, sortDir.name(), "grp_idx").toString()
			,getTable().getTableName(),getTable().getTableName(), "grp_idx"
		));
		return (S) this;
	}

	/**
	 * 将当前select对应的sql以select * from ()的方式包住，不需要通过WrapSelect构建子查询
	 * 调用该方法一般不直接toRowset，而是用于联表
	 */
	public S toSubSelect(){
		String newTableName = "("+this.getSQL().replaceAll("\\r?\\n"," ")+")";
		AbstractSelect<S, R> abstractSelect = esdk.reflect.safeNewInstance(this.getClass());
		abstractSelect.setTableName(newTableName);
		return (S) abstractSelect;
	}

	// 联表快捷入口
	private JoinSelect join(AbstractSelect select, JoinStrategy joinStrategy, String joinType){
		JoinSelect joinSelect = new JoinSelect(getConnection(), this, select, joinType, joinStrategy);
		joinSelect.setRowsOffset(this.getSelect().getRowsOffset());
		joinSelect.setRowsLimit(this.getSelect().getRowsLimit());
		return joinSelect;
	}
	public JoinSelect innerJoin(AbstractSelect select, JoinStrategy joinStrategy){
		return join(select, joinStrategy, JoinSelect.INNERJOIN);
	}
	public JoinSelect leftJoin(AbstractSelect select, JoinStrategy joinStrategy){
		return join(select, joinStrategy, JoinSelect.LEFTJOIN);
	}
	public JoinSelect rightJoin(AbstractSelect select, JoinStrategy joinStrategy){
		return join(select, joinStrategy, JoinSelect.RIGHTJOIN);
	}
	public JoinSelect fullJoin(AbstractSelect select, JoinStrategy joinStrategy){
		return join(select, joinStrategy, JoinSelect.FULLJOIN);
	}

	/**
	 * 指定索引
	 * @param indexs 支持多个索引，mysql会从这些索引中选择一个来进行查询
	 */
	public S useIndex(String ...indexs){
		if(indexs == null || indexs.length == 0) return (S)this;
		Table table=getTable();
		for(String index: indexs){
			table.useIndex(index);
		}
		return (S)this;
	}

	/**
	 * 强制索引
	 * @param indexs 支持多个索引，mysql会从这些索引中选择一个来进行查询
	 */
	public S forceIndex(String ...indexs){
		if(indexs == null || indexs.length == 0) return (S)this;
		Table table=getTable();
		for(String index: indexs){
			table.forceIndex(index);
		}
		return (S)this;
	}

	/**
	 * 通过join来优化查询速度<br>
	 * 当索引无法覆盖查询的列时，通过先查出主键，再根据主键关联自身，得到要查询的列，这种方式能避免回表，在一定程度上能提高查询性能<br>
	 * 调用optimize()正常不应该再对select进行操作了，应该直接toRowSet，optimize已经将原先sql解析并包装成子查询了<br>
	 */
	public S optimize(){
		Select newSelect = new Select();
		newSelect.setOriginSelect(select.clone());
		OrderBy[] orderBys=this.select.getOrderbys().toArray();
		Column[] columns=this.select.getColumns().toArray();
		boolean oldOutAll = isOutputAllFields;
		this.isOutputAllFields = false;
		String pkName = getPrimaryKeyFieldName();
		this.select.setColumns(pkName);
		parse(); // 转换后字段映射变成where条件
		this.fieldMap.clear(); // 清掉字段映射，避免后面parse再解析填充了多余的查询条件
		String newTableName = "(SELECT opt.* FROM %s opt JOIN (%s) mt ON opt.%s=mt.%s)".formatted(getTable().getTableName(), select.getSQL(), pkName, pkName);
		this.isOutputAllFields = !(columns.length == 1 && "*".equals(columns[0].toString())) && oldOutAll; // 原输出中没有*且之前就设置为输出全部的话，就输出全部
		Table table=new Table(newTableName);
		table.createAliasName();
		newSelect.setConnection(this.select.getConnection());
		newSelect.setTable(table);
		newSelect.setOrderby(Arrays.stream(orderBys).map(e->new OrderBy(new Field(table, e.getField().getName()))).toArray(OrderBy[]::new));
		// 修改字段的所属表名，字段输出的时候才能增加正确的别名
		for(int i=0;i<columns.length;i++){
			columns[i].setField(new Field(table, columns[i].getField().getName()));
		}
		newSelect.setColumns(columns);
		this.select = newSelect;
		return (S)this;
	}

	public WrapSelect wrapSelect(){
		return new WrapSelect(this,getTable().createAliasName());
	}

	/**
	 * 业务对一个select可能会有些通用的操作，比如加车场权限，通过这个方法，可以让附加的操作更可观，并简化代码量
	 * eg:
	 * 原先的：ParkingAuth.use(DF.xxxSelect()).toRowSet()
	 * 现在的：DF.xxxSelect().with(ParkingAuth::use).toRowSet()
	 * 也可以将Parking::use定义成一个变量，放在ProjectModule中
	 * Callback<AbstarctSelect> ParkingAuth = ParkingAuth::use;
	 * 更新后：DF.xxxSelect().with(ParkingAuth).toRowSet()
	 */
	public S with(Callback<AbstractSelect> func){
		func.invoke((S)this);
		return (S)this;
	}

	public long getExecuteConsuming(){
		return this.select.getExecuteConsuming();
	}
}
